package org.funny.machine32.instructionset;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.funny.machine32.base.Instruction;

import java.util.Map;
import java.util.Set;

public class Instructions {
    private Set<Instruction> instructionSet = Sets.newHashSet();

    public Map<String, Instruction> getCache() {
        return cache;
    }

    private Map<String, Instruction> cache = Maps.newHashMap();

    public void addInstruction(Instruction instruction) {
        instructionSet.add(instruction);
        cache.put(instruction.getMnemonic(), instruction);
    }


    private static Set<Instruction> instructions = initInstructions();
    private static BiMap<String, Byte> isCache = initCache(instructions);
    private static Map<String, Instruction> asmCache = initAsmCache(instructions);
    private static Map<Byte, Instruction> opCache = initOpCache(instructions);

    private static Map<String, Instruction> initAsmCache(Set<Instruction> instructions) {
        Map<String, Instruction> map = Maps.newHashMap();
        for (Instruction instruction : instructions) {
            map.put(instruction.getMnemonic(), instruction);
        }
        return map;
    }


    private static Map<Byte, Instruction> initOpCache(Set<Instruction> instructions) {
        Map<Byte, Instruction> map = Maps.newHashMap();
        for (Instruction instruction : instructions) {
            map.put(instruction.getOpcode(), instruction);
        }
        return map;
    }

    private static BiMap<String, Byte> initCache(Set<Instruction> instructions) {
        BiMap<String, Byte> map = HashBiMap.create();
        for (Instruction instruction : instructions) {
            map.put(instruction.getMnemonic(), instruction.getOpcode());
        }
        return map;
    }

    private static Set<Instruction> initInstructions() {
        Set<Instruction> is = Sets.newHashSet();
        addInstruction(is, "halt", (byte) 0x0); //停止执行
        addInstruction(is, "nop", (byte) 0x10); //无操作
        addInstruction(is, "rrmovq", (byte) 0x20); //传送指令
        addInstruction(is, "cmovle", (byte) 0x21); //传送指令
        addInstruction(is, "cmovl", (byte) 0x22); //传送指令
        addInstruction(is, "cmove", (byte) 0x23); //传送指令
        addInstruction(is, "cmovne", (byte) 0x24); //传送指令
        addInstruction(is, "cmovge", (byte) 0x25); //传送指令
        addInstruction(is, "cmovg", (byte) 0x26); //传送指令
        addInstruction(is, "irmovq", (byte) 0x30); //传送指令
        addInstruction(is, "rmmovq", (byte) 0x40); //传送指令
        addInstruction(is, "mrmovq", (byte) 0x50); //传送指令
        addInstruction(is, "addq", (byte) 0x60); //整数操作指令
        addInstruction(is, "subq", (byte) 0x61); //整数操作指令
        addInstruction(is, "andq", (byte) 0x62); //整数操作指令
        addInstruction(is, "xorq", (byte) 0x63); //整数操作指令
        addInstruction(is, "jmp", (byte) 0x70); //分支指令
        addInstruction(is, "jle", (byte) 0x71); //分支指令
        addInstruction(is, "jl", (byte) 0x72); //分支指令
        addInstruction(is, "je", (byte) 0x73); //分支指令
        addInstruction(is, "jne", (byte) 0x74); //分支指令
        addInstruction(is, "jge", (byte) 0x75); //分支指令
        addInstruction(is, "jg", (byte) 0x76); //分支指令
        addInstruction(is, "call", (byte) 0x80); //返回地址入栈并跳到目的地址
        addInstruction(is, "ret", (byte) 0x90); //从调用中返回
        addInstruction(is, "pushq", (byte) 0xA0); //入栈
        addInstruction(is, "popq", (byte) 0xB0); //出栈
        return is;
    }

    private static void addInstruction(Set<Instruction> set, String asmCode, Byte opCode) {
        Instruction instruction = new Instruction();
        instruction.setMnemonic(asmCode);
        instruction.setOpcode(opCode);
        set.add(instruction);
    }

    public static Byte getOpCode(String asmCode) {
        return isCache.get(asmCode);
    }

    public static String getAsmCode(Byte opCode) {
        return isCache.inverse().get(opCode);
    }

    public static Instruction getByOpCode(Byte opCode) {
        return opCache.get(opCode);
    }

    public static Instruction getByAsmCode(String asmCode) {
        return asmCache.get(asmCode);
    }
}
